使用Xterm.js做Log Viewer

Xterm.js是一个用来做Web Terminal的前端组件,VScode的terminal也是基于Xterm.js实现,它支持DOM、Canvas、WebGL渲染,能够快速渲染大量文字,最大支持2G的字符,本文介绍如何使用Xterm+Websocket来做一个Log Viewer。

项目背景

在日常工作中,服务器上会产生大量日志,比如数据库日志,服务运行日志,因此我们需要通过Web浏览器查看日志,需要包含的功能如下:

  • 初始请求(500条)最近的日志
  • 能够显示实时产生的日志
  • 向上滚动页面,能够查询之前产生的日志

初始请求

当进入页面时,我们需要发送一个Websocket请求到后端,比如请求最近的500条日志,后端根据日志的行数,每一行对应一条数据,每条数据需要带上该条日志在日志文件中的索引行数,响应给前端。然后前端通过xterm.write()渲染日志。

实时渲染

当初始请求结束之后,需要渲染实时产生的日志,当后端监听到日志文件的行数增加时,即有新的日志产生,便推送给前端,前端渲染出来,Xterm支持自动滚动到底部,因此页面便会有自动滚动到底部的效果。

加载之前的日志

当页面向上滚动,我们可以监听鼠标wheel事件,当滚动到顶部时,前端发送请求,我们需要将当前第一条的日志索引行数作为参数,再去请求该行之前的500条,返回给前端,由于Xterm不支持prepend数据,因为我们需要重新渲染所有日志,此时
需要加一个loading效果,由于Xterm支持5M/s的渲染速度,因此重新渲染也会非常快速的完成,整个loading的等待时间并不会久。由于xterm.write()是异步的,我们可以在渲染完最后一条日志之后,认定他渲染完成,重新渲染完之后,我们再滚动到请求之前的行数即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
//  Fake code
document.addEventListener('wheel', async () => {
if(xterm.toTop()){
const data = await requestAPI(logs[0].lineIndex);
const newLogs = [...data, ...logs];
newLogs.forEach((log, i) => {
xterm.write(log);
if(i === newLogs.length - 1){
xterm.scrollToLine(data.length);
}
});
}
})

大量数据场景的优化

如果一直向上翻动页面,会不停地prepend日志,当超过3W条之后,能够明显感觉到loading的效果超过了1秒,因此在向上翻动时,我们可以只渲染新加载的日志,而把看不到的日志放到数组中,等页面向下滚动时,再渲染出来。

而实时渲染的日志由于不需要重新渲染,因此数据量过大不需要理会,因为之前已经渲染的文本会被xterm自动缓存起来。

坑点

  • xterm的api支持监听xterm滚动事件,但是鼠标滚动和实时数据渲染滚动都会触发它的滚动事件,因此我们需要用鼠标的wheel来监听,因为当因为日志增加时,自身的渲染触发滚动事件并不是我们期望的。
  • 在手机端上使用Canvas渲染时,xterm并没有加入DPR的逻辑,因此渲染出来的文字大小会翻倍,所以在手机端还是需要切换成DOM渲染。
  • 由于我们既要请求实时数据,又要请求之前的数据,且是用的同一个websocket连接,当向上滚动时,我们就暂停请求实时数据,因为即便请求回来,它也不在用户的可视范围之内,
    当用户向下滚动到底部时,再去请求即可。

(完)